//The MIT License (MIT)
//
//Copyright (c) 2020 lihp1603
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

package monitor

import (
	"time"

	"gitee.com/lihaiping1603/monitor/proc"
)

//系统状态
type SystemStats struct {
	CpuMI     *CpuMonitorInfo        `json:"cpu"`
	VmMI      *VmMonitorInfo         `json:"vm"`
	DiskMI    *DiskMonitorInfo       `json:"disk"`
	MemMI     *MemMonitorInfo        `json:"mem"`
	UptimeMI  *UptimeMonitorInfo     `json:"uptime"`
	LoadavgMI *LoadavgMonitorInfo    `json:"loadavg"`
	NetDevMI  *NetDevicesMonitorInfo `json:"network_devcice"`
}

func NewSystemStats(disk_catalog, disk_name string) (*SystemStats, error) {
	sys_stat := &SystemStats{}
	cmi, err := NewCpuMonitorInfo()
	if err != nil {
		return nil, err
	}
	vmmi, err := NewVmMonitorInfo()
	if err != nil {
		return nil, err
	}
	dmi, err := NewDiskMonitorInfo(disk_catalog, disk_name)
	if err != nil {
		return nil, err
	}
	mmi, err := NewMemMonitorInfo()
	if err != nil {
		return nil, err
	}
	uptimemi, err := NewUptimeMonitorInfo()
	if err != nil {
		return nil, err
	}
	lmi, err := NewLoadavgMonitorInfo()
	if err != nil {
		return nil, err
	}
	ndi, err := NewNetDevicesMonitorInfo()
	if err != nil {
		return nil, err
	}
	//
	sys_stat.CpuMI = cmi
	sys_stat.VmMI = vmmi
	sys_stat.DiskMI = dmi
	sys_stat.MemMI = mmi
	sys_stat.UptimeMI = uptimemi
	sys_stat.LoadavgMI = lmi
	sys_stat.NetDevMI = ndi

	return sys_stat, nil
}

func (sysstat *SystemStats) GetCpuMI() *CpuMonitorInfo {
	return sysstat.CpuMI
}

var (
	sys_cpu_info *proc.CPUInfo = nil
)

//更新系统cpu信息
func InitSysCpuInfo() error {
	if sys_cpu_info != nil {
		return nil
	}
	cpu_info, err := proc.ReadCPUInfo()
	if err != nil {
		return err
	}
	sys_cpu_info = cpu_info
	return nil
}

func init() {
	if err := InitSysCpuInfo(); err != nil {
		LogErr("init system cpu info:", err)
	}
}

// UsagePctCPU is an object that represents the average cpu usage over a
// time period. It is calculated by taking the difference between two
// CPUSamples (whose units are clock ticks), dividing by the number of elapsed
// ticks between the samples, and converting to a percent.
type CpuMonitorInfo struct {
	cpustat         proc.CPUStat
	CpuUsagePercent float64 `json:"cpu_percent"` //系统cpu使用率
	Cpus            int16   `json:"cpus"`        //处理器数量
	CpuOnline       int16   `json:"cpus_online"` //可用的cpu数量
}

func NewCpuMonitorInfo() (*CpuMonitorInfo, error) {
	cmi := &CpuMonitorInfo{}
	if err := cmi.UpdateMonitorInfo(); err != nil {
		return nil, err
	}
	return cmi, nil
}

func (cmi *CpuMonitorInfo) UpdateMonitorInfo() error {
	cur_cpus_stat, err := proc.ReadProcSystemStat()
	if err != nil {
		return err
	}
	cur_cpu_stat := cur_cpus_stat.GetCpuStatAll()
	cmi.calculateUsagePercent(cur_cpu_stat)
	cmi.cpustat = *cur_cpu_stat
	//
	if cmi.Cpus <= 0 {
		cpu_info, err := proc.ReadCPUInfo()
		if err != nil {
			return err
		}
		cmi.Cpus = int16(cpu_info.NumCPU())
		cmi.CpuOnline = cmi.Cpus
	}
	return nil
}

func (cmi *CpuMonitorInfo) calculateUsagePercent(cur *proc.CPUStat) {
	if cmi.cpustat.GetCpuTotal() <= 0 {
		return
	}
	total := float64(cur.GetCpuTotal() - cmi.cpustat.GetCpuTotal())
	idled := float64(cur.GetCpuIdle() - cmi.cpustat.GetCpuIdle())

	invQuotient := 100.00 / total
	cmi.CpuUsagePercent = (total - idled) * invQuotient
}

type CpuStatPercent struct {
	UserPct    float64
	NicePct    float64
	SystemPct  float64
	IdlePct    float64
	IowaitPct  float64
	IrqPct     float64
	SoftIrqPct float64
	StealPct   float64
	GuestPct   float64
}

//计算这段时间内的cpu平均指标百分比
func CalculateCPUStat(second, first proc.CPUStat) (res CpuStatPercent) {
	total := float64(second.GetCpuTotal() - first.GetCpuTotal())
	//idled := float64(second.GetCpuIdle() - first.GetCpuIdle())

	invQuotient := 100.00 / total

	//differentiate: actual value minus the previous one
	res.UserPct = subtractAndConvertTicks(second.User, first.User) * invQuotient
	res.NicePct = subtractAndConvertTicks(second.Nice, first.Nice) * invQuotient
	res.SystemPct = subtractAndConvertTicks(second.System, first.System) * invQuotient
	res.IdlePct = subtractAndConvertTicks(second.Idle, first.Idle) * invQuotient
	res.IowaitPct = subtractAndConvertTicks(second.IOWait, first.IOWait) * invQuotient
	res.IrqPct = subtractAndConvertTicks(second.IRQ, first.IRQ) * invQuotient
	res.SoftIrqPct = subtractAndConvertTicks(second.SoftIRQ, first.SoftIRQ) * invQuotient
	res.StealPct = subtractAndConvertTicks(second.Steal, first.Steal) * invQuotient
	res.GuestPct = subtractAndConvertTicks(second.Guest, first.Guest) * invQuotient

	return
}

func subtractAndConvertTicks(second, first uint64) float64 {
	return float64(second - first)
}

type VmMonitorInfo struct {
	vmstat      proc.VMStat
	VmReadKBps  int64 `json:"disk_read_KBps"`  //读速度
	VmWriteKBps int64 `json:"disk_write_KBps"` //写速度
}

func NewVmMonitorInfo() (*VmMonitorInfo, error) {
	vmmi := &VmMonitorInfo{}
	if err := vmmi.UpdateMonitorInfo(); err != nil {
		return nil, err
	}
	return vmmi, nil
}

func (vmmi *VmMonitorInfo) UpdateMonitorInfo() error {
	cur_vmstat, err := proc.ReadVMStat()
	if err != nil {
		return err
	}
	vmmi.calculate(cur_vmstat)
	vmmi.vmstat = *cur_vmstat
	return nil
}

func (vmmi *VmMonitorInfo) calculate(cur *proc.VMStat) {
	last := vmmi.vmstat
	if last.GetPagePagein() <= 0 || last.GetPagePageout() <= 0 {
		return
	}
	// KBps = KB * 1000 / ms = KB/s
	duration_ms := cur.GetSampleTime().Sub(last.GetSampleTime()).Milliseconds()
	invQuotient := 1000 / duration_ms
	vmmi.VmReadKBps = int64(cur.GetPagePagein()-last.GetPagePagein()) * invQuotient
	vmmi.VmWriteKBps = int64(cur.GetPagePageout()-last.GetPagePageout()) * invQuotient
	return
}

type DiskMonitorInfo struct {
	path            string        //监控的盘符路径
	diskName        string        //监控状态的磁盘名 divece name
	diskstat        proc.DiskStat //
	DiskBusyPercent float64       `json:"disk_busy_percent"`  //磁盘繁忙度
	DiskUsedPercent float64       `json:"disk_usage_percent"` //磁盘容量占比
	DiskCapacityMb  uint64        `json:"disk_capacity_mb"`   //磁盘容量
	DiskUsedMb      uint64        `json:"disk_used_mb"`       //磁盘用了
	DiskFreeMb      uint64        `json:"disk_free_mb"`       //磁盘空闲
}

func NewDiskMonitorInfo(disk_path, disk_name string) (*DiskMonitorInfo, error) {
	if disk_path == "" {
		disk_path = "/"
	}
	if disk_name == "" {
		disk_name = "sda"
	}
	diskmi := &DiskMonitorInfo{path: disk_path, diskName: disk_name}
	if err := diskmi.UpdateMonitorInfo(); err != nil {
		return nil, err
	}
	return diskmi, nil
}

func (dmi *DiskMonitorInfo) UpdateMonitorInfo() error {
	disk_path := dmi.path
	disk_capacity, err := proc.ReadDisk(disk_path)
	if err != nil {
		return err
	}
	dmi.calculateDiskCapacity(disk_capacity)
	//disk stat
	disk_stats, err := proc.ReadDiskStats()
	if err != nil {
		return err
	}
	for _, disk_stat := range disk_stats {
		if disk_stat.GetName() == dmi.diskName {
			//计算busy度
			dmi.calculateDiskBusyPercent(&disk_stat)
			dmi.diskstat = disk_stat
		}
	}
	return nil
}

const (
	SYS_KB = 1024
	SYS_MB = 1024 * SYS_KB
)

func (dmi *DiskMonitorInfo) calculateDiskCapacity(cur *proc.Disk) {
	if cur.GetTotalCapacityByte() <= 0 {
		return
	}
	dmi.DiskCapacityMb = cur.GetTotalCapacityByte() / SYS_MB
	dmi.DiskUsedMb = cur.GetUsedByte() / SYS_MB
	dmi.DiskFreeMb = cur.GetFreeByte() / SYS_MB

	dmi.DiskUsedPercent = 100.0 * float64(dmi.DiskUsedMb) / float64(dmi.DiskCapacityMb)
	return
}

func (dmi *DiskMonitorInfo) calculateDiskBusyPercent(cur_disk_stat *proc.DiskStat) {
	last_disk_stat := dmi.diskstat
	if last_disk_stat.GetIOTicks() <= 0 || cur_disk_stat.GetIOTicks() <= 0 {
		return
	}
	delta_ms := cur_disk_stat.GetSampleTime().Sub(last_disk_stat.GetSampleTime()).Milliseconds()
	io_delta_ticks := cur_disk_stat.GetIOTicks() - last_disk_stat.GetIOTicks()
	io_delta_ms := io_delta_ticks.Milliseconds()
	busy_percent := 100.0 * float64(io_delta_ms) / float64(delta_ms)
	dmi.DiskBusyPercent = busy_percent
	return
}

type MemMonitorInfo struct {
	MemRamKByte    uint64  `json:"mem_ram_kbyte"`    //内存大小
	MemRamPercent  float64 `json:"mem_ram_percent"`  //内存使用率
	MemSwapKByte   uint64  `json:"mem_swap_kbyte"`   //swap内存大小
	MemSwapPercent float64 `json:"mem_swap_percent"` //swap使用率
}

func NewMemMonitorInfo() (*MemMonitorInfo, error) {
	mmi := &MemMonitorInfo{}
	if err := mmi.UpdateMonitorInfo(); err != nil {
		return nil, err
	}
	return mmi, nil
}

func (mmi *MemMonitorInfo) UpdateMonitorInfo() error {
	cur_mem_info, err := proc.ReadMemInfo()
	if err != nil {
		return err
	}
	mmi.calculateUsage(cur_mem_info)
	return nil
}

func (mmi *MemMonitorInfo) calculateUsage(cur_mem_info *proc.MemInfo) {
	mem_total := cur_mem_info.GetMemTotalKB()
	mem_free := cur_mem_info.GetMemFreeKB()
	mem_buffered := cur_mem_info.GetMemBuffersKB()
	mem_cached := cur_mem_info.GetMemCachedKB()

	mem_swaped_total := cur_mem_info.GetSwapTotalKB()
	mem_swaped_free := cur_mem_info.GetSwapFreeKB()

	mem_active := mem_total - mem_free
	mem_inused := mem_active - mem_buffered - mem_cached
	//mem_notused := mem_total - mem_inused

	if mem_total > 0 {
		used_percent := 100.0 * float64(mem_inused) / float64(mem_total)
		mmi.MemRamPercent = used_percent
		mmi.MemRamKByte = mem_total
	}
	if mem_swaped_total > 0 {
		mmi.MemSwapKByte = mem_swaped_total
		mmi.MemSwapPercent = 100.0 * (1.0 - float64(mem_swaped_free)/float64(mem_swaped_total))
	}
	return
}

type UptimeMonitorInfo struct {
	UptimeIdlePercent float64 `json:"ilde_percent"` //uptime衡量系统自启动以来的空闲度
	UptimeTotalSec    uint64  `json:"uptime"`       //自系统启动来的时间
	UptimeIdleSec     uint64  `json:"ilde_time"`    //空闲时间
}

func NewUptimeMonitorInfo() (*UptimeMonitorInfo, error) {
	utmi := &UptimeMonitorInfo{}
	if err := utmi.UpdateMonitorInfo(); err != nil {
		return nil, err
	}
	return utmi, nil
}

func (utmi *UptimeMonitorInfo) UpdateMonitorInfo() error {
	uptime, err := proc.ReadUptime()
	if err != nil {
		return err
	}
	utmi.UptimeTotalSec = uint64(uptime.GetTotalDuration())
	utmi.UptimeIdleSec = uint64(uptime.GetIdleDuration())
	if sys_cpu_info != nil {
		utmi.UptimeIdlePercent = uptime.CalculateIdlePercent(int16(sys_cpu_info.NumCPU()))
	}
	return nil
}

type LoadavgMonitorInfo struct {
	Loadavg1Min  float64 `json:"load_1m"` //最近1min的负荷情况
	Loadavg5min  float64 `json:"load_5m"`
	Loadavg15min float64 `json:"load_15m"`
}

func NewLoadavgMonitorInfo() (*LoadavgMonitorInfo, error) {
	lmi := &LoadavgMonitorInfo{}
	if err := lmi.UpdateMonitorInfo(); err != nil {
		return nil, err
	}
	return lmi, nil
}

func (lmi *LoadavgMonitorInfo) UpdateMonitorInfo() error {
	loadavg, err := proc.ReadLoadAvg()
	if err != nil {
		return err
	}
	lmi.Loadavg1Min = loadavg.GetLast1Min()
	lmi.Loadavg5min = loadavg.GetLast5Min()
	lmi.Loadavg15min = loadavg.GetLast15Min()
	return nil
}

type NetDevicesMonitorInfo struct {
	NetSampleTime     time.Time `json:"net_sample_time"` //网络数据采集时间
	NetRecvBytePublic uint64    `json:"net_recv_bytes"`  //公有网络数据
	NetSendBytePublic uint64    `json:"net_send_bytes"`
	NetRecvBytePriv   uint64    `json:"net_recvi_bytes"` //私有网络数据
	NetSendBytePriv   uint64    `json:"net_sendi_bytes"`
}

func NewNetDevicesMonitorInfo() (*NetDevicesMonitorInfo, error) {
	ndmi := &NetDevicesMonitorInfo{}
	if err := ndmi.UpdateMonitorInfo(); err != nil {
		return nil, err
	}
	return ndmi, nil
}

func (ndmi *NetDevicesMonitorInfo) UpdateMonitorInfo() error {
	netdev_stats, err := proc.ReadNetDevStat()
	if err != nil {
		return err
	}
	var (
		privRecvByte uint64 = 0
		privSendByte uint64 = 0
		pubRecvByte  uint64 = 0
		pubSendByte  uint64 = 0
	)
	sampletime := time.Now()
	for _, netdev_stat := range netdev_stats {
		if netdev_stat.GetIfaceName() == "lo" { //
			privRecvByte += netdev_stat.GetRxBytes()
			privSendByte += netdev_stat.GetTxBytes()
			continue
		}
		pubRecvByte += netdev_stat.GetRxBytes()
		pubSendByte += netdev_stat.GetTxBytes()
		sampletime = netdev_stat.GetSampleTime()
	}

	ndmi.NetRecvBytePriv = privRecvByte
	ndmi.NetSendBytePriv = privSendByte
	ndmi.NetRecvBytePublic = pubRecvByte
	ndmi.NetSendBytePublic = pubSendByte
	ndmi.NetSampleTime = sampletime
	return nil
}
